package com.tul.vapp.servicecenter.service.impl;

import com.tul.cusa.common.util.ListUtil;
import com.tul.vapp.servicecenter.entity.App;
import com.tul.vapp.servicecenter.entity.Item;
import com.tul.vapp.servicecenter.entity.Namespace;
import com.tul.vapp.servicecenter.entity.Status;
import com.tul.vapp.servicecenter.service.IAppService;
import com.tul.vapp.servicecenter.service.IItemService;
import com.tul.vapp.servicecenter.service.ILogReleaseService;
import com.tul.vapp.servicecenter.service.IManager;
import com.tul.vapp.servicecenter.service.INamespaceService;
import com.tul.vapp.servicecenter.service.IZkService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;

/**
 * 通过此类保证DB数据和ZK数据一致性
 *
 * @author vv
 * @since 2017/12/30.
 */
@Service
public class Manager implements IManager {

    private static final Logger LOGGER = LoggerFactory.getLogger(Manager.class);

    private static final String USER_NAME = "admin";

    @Autowired
    private IZkService zkService;

    @Autowired
    private IAppService appService;

    @Autowired
    private INamespaceService namespaceService;

    @Autowired
    private IItemService itemService;

    @Autowired
    private ILogReleaseService logService;

    /**
     * 配置管理器初始化
     */
    @Override
    public void sync() {
        syncApp();
        LOGGER.info("Synchronized DB data to ZK.");
    }

    /**
     * 同步DB和ZK的APP节点数据
     */
    private void syncApp() {

        // 找出数据库相对于ZK的新增项和删除项
        List<App> apps = appService.getReleaseApps();
        List<String> appsDb = new ArrayList<>();
        for (App a : apps) {
            appsDb.add(a.getAppId());
        }
        List<String> appsZk = zkService.getApps();
        List<String> adds = ListUtil.subtract(appsDb, appsZk);
        List<String> deletes = ListUtil.subtract(appsZk, appsDb);

        // 向ZK上添加新增项
        for (String add : adds) {
            zkService.createApp(add);
        }

        // 删除未在数据库中存在的
        for (String delete : deletes) {
            zkService.deleteApp(delete);
        }

        // 对于每个应用，初始化其下namespace节点
        for (String a : appsDb) {
            syncNamespace(a);
        }

    }

    private void syncNamespace(String appId) {
        List<Namespace> nsObj = namespaceService.getReleaseNamespaces(appId);
        List<String> nsDb = new ArrayList<>();
        for (Namespace obj : nsObj) {
            nsDb.add(obj.getNamespace());
        }

        List<String> nsZk = zkService.getNamespace(appId);
        List<String> adds = ListUtil.subtract(nsDb, nsZk);
        List<String> deletes = ListUtil.subtract(nsZk, nsDb);
        List<String> sames = ListUtil.intersection(nsDb, nsZk);

        // 向ZK上添加新增项
        for (String add : adds) {
            List<Item> items = itemService.getReleaseItems(appId, add);
            String content = itemsToString(items);
            zkService.createNamespace(appId, add, content);
        }

        // 删除未在数据库中存在的
        for (String delete : deletes) {
            zkService.deleteNamespace(appId, delete);
        }

        for (String same : sames) {
            List<Item> items = itemService.getReleaseItems(appId, same);
            String contentDb = itemsToString(items);
            String contentZk = zkService.getItems(appId, same);
            // 内容不同，则通知ZK更新
            if (!contentDb.equals(contentZk)) {
                zkService.notifyZk(appId, same, contentDb);
            }
        }
    }

    private String itemsToString(List<Item> items) {
        if (items == null) {
            return "";
        }
        StringBuilder sb = new StringBuilder();
        for (Item item : items) {
            sb.append(item.toString()).append("\n");
        }
        return sb.toString();
    }

    @Override
    @Transactional
    public synchronized void publish() {

        // 发布app
        List<App> apps = appService.getApps();
        for (App app : apps) {
            publishApp(app.getAppId());
        }

        // 发布namespace
        List<Namespace> namespaces = namespaceService.getNamespaces();
        for (Namespace ns : namespaces) {
            publishNamespace(ns.getAppId(), ns.getNamespace());
        }

        // 发布item
        List<Namespace> itemChangedNs = itemService.getModifiedNamespace();
        for (Namespace ns : itemChangedNs) {
            publishItems(ns.getAppId(), ns.getNamespace());
        }
        LOGGER.info("Published all config data.");
    }

    @Override
    @Transactional
    public void publish(String appId, String namespace) {
        publishApp(appId);
        publishNamespace(appId, namespace);
        String configurations = publishItems(appId, namespace);
        logService.log(appId, namespace, configurations, USER_NAME);
    }

    private void publishApp(String appId) {
        // 发布app
        App app = appService.getApp(appId);
        if (app == null || app.getStatus() == Status.PUBLISHED) {
            // 未更改的不做处理
            return;
        }
        // ZK只处理新增和删除
        if (app.getStatus() != Status.DELETE) {
            zkService.createApp(appId);
        } else if (app.getStatus() == Status.DELETE) {
            zkService.deleteApp(appId);
        }
        // 数据库更新到发布状态
        appService.publish(appId);
    }

    private void publishNamespace(String appId, String namespace) {
        // 发布namespace
        Namespace nsObj = namespaceService.getNamespace(appId, namespace);
        if (nsObj.getStatus() == Status.PUBLISHED) {
            // 未更改的不做处理
            return;
        }
        // ZK只处理新增和删除
        if (nsObj.getStatus() != Status.DELETE) {
            zkService.createNamespace(appId, namespace, "");
        } else if (nsObj.getStatus() == Status.DELETE) {
            zkService.deleteNamespace(appId, namespace);
        }
        // 数据库更新到发布状态
        namespaceService.publish(appId, namespace);
    }

    private String publishItems(String appId, String namespace) {
        // 发布item
        StringBuilder sb = new StringBuilder();
        List<Item> items = itemService.getItems(appId, namespace);
        for (Item it : items) {
            // 找出准备同步到ZK的配置
            int status = it.getStatus();
            if (status == Status.PUBLISHED || status == Status.INSERT || status == Status.MODIFY) {
                sb.append(it).append("\n");
            }
        }
        // 修改ZK上对应的配置项
        zkService.notifyZk(appId, namespace, sb.toString());
        // 命名空间下所有的配置发布
        itemService.publish(appId, namespace);
        return sb.toString();
    }

    @Override
    @Transactional
    public synchronized void rollback() {
        // 回滚app
        List<App> apps = appService.getApps();
        for (App app : apps) {
            appService.rollback(app.getAppId());
        }

        // 发布namespace
        List<Namespace> namespaces = namespaceService.getNamespaces();
        for (Namespace ns : namespaces) {
            namespaceService.rollback(ns.getAppId(), ns.getNamespace());
        }

        // 发布item
        List<Namespace> itemChangedNs = itemService.getModifiedNamespace();
        for (Namespace ns : itemChangedNs) {
            itemService.rollback(ns.getAppId(), ns.getNamespace());
        }
    }

    @Override
    public void sanityCheck() {
        appService.sanityCheck();
        List<App> apps = appService.getReleaseApps();
        for (App app : apps) {
            namespaceService.sanityCheck(app.getAppId());
            List<Namespace> ns = namespaceService.getNamespaces(app.getAppId());
            for (Namespace n : ns) {
                itemService.sanityCheck(n.getAppId(), n.getNamespace());
            }
        }
        LOGGER.info("Sanity checked for operation table.");
    }

}
